/*
 * Copyright (C) 2012-2013  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.launch.swing;

import java.awt.Color;
import java.awt.SplashScreen;
import java.beans.EventHandler;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.StringTokenizer;

import javax.swing.SwingUtilities;

import nemos.launch.progress.IProgressDisplay;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;

/**
 * Activator : The bundle activator.
 * 
 * @author Romain DUBOIS
 */
public class Activator implements BundleActivator, IProgressDisplay, Runnable {

    /** The invalid path message */
    private static final String INVALID_PATH = "property.path.error"; //$NON-NLS-1$
    /** The invalid color message */
    private static final String INVALID_COLOR = "property.color.error"; //$NON-NLS-1$
    /** The image error message */
    private static final String IMAGE_ERROR = "image.load.error"; //$NON-NLS-1$

    /** The property name for the progress bar color */
    private static final String PROGRESS_EMPTY_COLOR = "nemos.launch.swing.progress.empty"; //$NON-NLS-1$
    /** The property name for the progress bar background color */
    private static final String PROGRESS_FILLED_COLOR = "nemos.launch.swing.progress.filled"; //$NON-NLS-1$
    /** The property name for the progress bar color */
    private static final String TEXT_EMPTY_COLOR = "nemos.launch.swing.text.empty"; //$NON-NLS-1$
    /** The property name for the progress bar background color */
    private static final String TEXT_FILLED_COLOR = "nemos.launch.swing.text.filled"; //$NON-NLS-1$
    /** The property name for the splashscreen background image */
    private static final String BACKGROUND = "nemos.launch.swing.background"; //$NON-NLS-1$
    /** The default splashscreen background image path */
    private static final String DEFAULT_BACKGROUND = "nemos/launch/swing/defaultBG.png"; //$NON-NLS-1$

    /** The default progress color */
    private static final Color DEFAULT_FILL_PROGRESS_COLOR = new Color(32, 144, 128);
    /** The default empty text color */
    private static final Color DEFAULT_EMPTY_TEXT_COLOR = new Color(100, 130, 210);

    /** The logger service */
    private Object mLogger;
    /** The bundle localized messages */
    private ResourceBundle mRsrcBundle;

    /** The splashscreen */
    ISplashScreen mSplashScreen;
    /** The current progress */
    private int mProgress;
    /** The last displayed label */
    private String mLastLabel;
    /** The bundle context */
    private BundleContext mContext;

    /**
     * Constructor
     */
    public Activator() {
        super();

        mProgress = 0;
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext)
     */
    @Override
    public void start(final BundleContext pContext) {
        mContext = pContext;
        mContext.registerService(IProgressDisplay.class, this, null);
    }

    /**
     * {@inheritDoc}
     * 
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    @Override
    public void stop(@SuppressWarnings("unused") final BundleContext pContext) {
        // Nothing to do
    }

    /**
     * Get an URL from the properties.
     * 
     * @param pBasePath
     *            The base file path
     * @param pLocale
     *            The Locale
     * @return The URL property value
     */
    private URL getUrl(final String pBasePath, final Locale pLocale) {
        // Find extension and root file path
        String lRoot = pBasePath;
        String lExt = ""; //$NON-NLS-1$
        final int lDot = pBasePath.lastIndexOf('.');
        if (lDot != -1) {
            lRoot = pBasePath.substring(0, lDot);
            lExt = pBasePath.substring(lDot);
        }

        // Compute localized paths
        final Deque<String> lPossibilities = new LinkedList<String>();
        // Default path
        lPossibilities.addFirst(pBasePath);

        final StringBuilder lBuilder = new StringBuilder(lRoot);
        // Path with locale
        if (pLocale.getLanguage().length() > 0) {
            lBuilder.append('_').append(pLocale.getLanguage());
            lPossibilities.addFirst(lBuilder.toString() + lExt);

            // Path with country
            if (pLocale.getCountry().length() > 0) {
                lBuilder.append('_').append(pLocale.getCountry());
                lPossibilities.addFirst(lBuilder.toString() + lExt);

                // Path with variant
                if (pLocale.getVariant().length() > 0) {
                    lBuilder.append('_').append(pLocale.getVariant());
                    lPossibilities.addFirst(lBuilder.toString() + lExt);
                }
            }
        }

        // Look for each path until one is valid
        URL lUrl = null;
        while (lUrl == null && !lPossibilities.isEmpty()) {

            final String lPath = lPossibilities.removeFirst();
            lUrl = mContext.getBundle().getResource(lPath);

            if (lUrl == null) {
                final File lExternalFile = new File(lPath);
                if (lExternalFile.exists()) {
                    try {
                        lUrl = lExternalFile.toURI().toURL();
                    } catch (final MalformedURLException e) {

                        // Only with last try
                        if (lPossibilities.isEmpty()) {
                            error(INVALID_PATH, e, BACKGROUND, pBasePath);
                        }
                    }
                }
            }
        }

        return lUrl;
    }

    /**
     * Log an error message.
     * 
     * @param pKey
     *            The message key
     * @param pException
     *            The exception to log
     * @param pParams
     *            The message parameters
     */
    private void error(final String pKey, final Throwable pException, final Object... pParams) {
        if (mLogger != null) {
            String lText = null;
            try {
                lText = mRsrcBundle.getString(pKey);
                lText = MessageFormat.format(lText, pParams);
            } catch (final Exception e) {
                lText = pKey + Arrays.toString(pParams);
            }
            ((LogService) mLogger).log(LogService.LOG_ERROR, lText, pException);
        }
    }

    /**
     * Collect a color from properties.
     * 
     * @param pProperty
     *            The property name
     * @param pContext
     *            The context to use
     * @param pDefault
     *            The default color
     * @return The color property value
     */
    private Color getColor(final String pProperty, final BundleContext pContext,
            final Color pDefault) {
        final String lValue = pContext.getProperty(pProperty);

        Color lColor = null;
        if (lValue != null) {
            // Try to find a Color constant value
            try {
                final Field lField = Color.class.getField(lValue);
                if (Color.class.equals(lField.getType())
                        && Modifier.isStatic(lField.getModifiers())) {
                    lColor = (Color) lField.get(Color.class);
                }
            } catch (final Exception e) {
                // Not a constant
            }

            // Try with integers
            if (lColor == null) {
                try {
                    final StringTokenizer lTokens = new StringTokenizer(lValue, String.valueOf(','));
                    if (lTokens.countTokens() == 1) {
                        lColor = new Color(Integer.parseInt(lTokens.nextToken().trim(), 16));
                    } else if (lTokens.countTokens() == 3) {
                        final int lRed = Integer.parseInt(lTokens.nextToken());
                        final int lGreen = Integer.parseInt(lTokens.nextToken());
                        final int lBlue = Integer.parseInt(lTokens.nextToken());

                        lColor = new Color(lRed, lGreen, lBlue);
                    }
                } catch (final Exception e) {
                    // Not a constant
                }
            }

            if (lColor == null) {
                error(INVALID_COLOR, null, pProperty, lValue);
            }
        }

        if (lColor == null) {
            lColor = pDefault;
        }
        return lColor;
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.launch.progress.IProgressDisplay#setLaunchLocale(java.util.Locale)
     */
    @Override
    public void setLaunchLocale(final Locale pLocale) {
        // Try to get logger service
        final ServiceReference<?> lReference = mContext
                .getServiceReference("org.osgi.service.log.LogService"); //$NON-NLS-1$
        if (lReference != null) {
            mLogger = mContext.getService(lReference);
        }

        // Collect labels file
        String lLocalizationFile = mContext.getBundle().getHeaders()
                .get(Constants.BUNDLE_LOCALIZATION);
        if (lLocalizationFile == null) {
            lLocalizationFile = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
        }
        mRsrcBundle = ResourceBundle.getBundle(lLocalizationFile, Locale.getDefault(),
                Activator.class.getClassLoader());

        try {
            // Find background image, if any
            final SplashScreen lSplashScreen = SplashScreen.getSplashScreen();
            final String lDefaultPath = mContext.getProperty(BACKGROUND);
            URL lBackground = null;
            if (lDefaultPath != null && !lDefaultPath.isEmpty()) {
                lBackground = getUrl(lDefaultPath, pLocale);
            }
            if (lBackground == null && lSplashScreen == null) {
                lBackground = getUrl(DEFAULT_BACKGROUND, pLocale);
            }

            // Init splashscreen
            if (lSplashScreen != null && lSplashScreen.isVisible()) {
                mSplashScreen = new SplashScreenAdaptor(lSplashScreen);
            } else {
                mSplashScreen = new SwingSplashScreen();
            }
            if (lBackground != null) {
                try {
                    mSplashScreen.setBackgroundImage(lBackground);
                } catch (final IOException e) {
                    error(IMAGE_ERROR, e, lBackground.toString());
                }
            }

            // Then configure it
            mSplashScreen.setEmptyProgressColor(getColor(PROGRESS_EMPTY_COLOR, mContext,
                    Color.WHITE));
            mSplashScreen.setEmptyTextColor(getColor(TEXT_EMPTY_COLOR, mContext,
                    DEFAULT_EMPTY_TEXT_COLOR));
            mSplashScreen.setFilledProgressColor(getColor(PROGRESS_FILLED_COLOR, mContext,
                    DEFAULT_FILL_PROGRESS_COLOR));
            mSplashScreen.setFilledTextColor(getColor(TEXT_FILLED_COLOR, mContext, Color.WHITE));

        } finally {

            // Release logger if found
            if (mLogger != null) {
                mContext.ungetService(lReference);
            }
        }
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.launch.progress.IProgressDisplay#setLabel(java.lang.String)
     */
    @Override
    public void setLabel(final String pLabel) {
        mLastLabel = pLabel;
        update();
    }

    /**
     * {@inheritDoc}
     * 
     * @see nemos.launch.progress.IProgressDisplay#setProgressValue(int)
     */
    @Override
    public void setProgressValue(final int pProgressValue) {
        mProgress = pProgressValue;
        update();
    }

    /**
     * Update the splashscreen.
     */
    private void update() {
        SwingUtilities.invokeLater(this);

        if (mProgress == 100) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (final InterruptedException e) {
                        // Ignore : thread will shutdown soon
                    }
                    SwingUtilities.invokeLater(EventHandler.create(Runnable.class, mSplashScreen,
                            "close")); //$NON-NLS-1$        
                }
            }).start();
        }
    }

    /**
     * Close the monitor.
     */
    public void close() {
        mSplashScreen.close();
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {
        mSplashScreen.setProgress(mProgress, mLastLabel);
    }
}
